﻿namespace hedefgrup.reports.renault
{
    using hedefgrup.reports.renault.calculators;
    using hedefgrup.reports.renault.database;
    using hedefgrup.reports.renault.date;
    using hedefgrup.reports.renault.definitions;
    using hedefgrup.reports.renault.implementation;
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Xml;
    using System.Runtime.CompilerServices;
    using System.IO;
    using System.Diagnostics;
    using System.Threading;

    public class dataCache
    {
        public static string dataDocumentPath;
        public static XmlDocument dataDocument;
        public static SortedList<string, dataCache> namedInstances = new SortedList<string, dataCache>();
        public static List<dataCache> orderedInstances = new List<dataCache>();
        public static SortedList<string, DataTable> dataTables = new SortedList<string, DataTable>();

        public List<reportMonth> availableData = new List<reportMonth>();
        public SortedList<string, dataColumn> columnDefinitions = new SortedList<string, dataColumn>();
        public SortedList<string, dealerDefinition> dealers = new SortedList<string, dealerDefinition>();
        public SortedList<string, questionDefinition> questionDefinitions = new SortedList<string, questionDefinition>();
        public SortedList<string, reportDefinition> reportDefinitions = new SortedList<string, reportDefinition>();
        public XmlDocument xmlData;
        public XmlNode dataNode;
        public reportCalculator reportCalculator;
        private string _organizationDefinitionFilePath;

        private bool isReady;

        private static SortedList<int, List<XmlNode>> definitionFileNodeCache = new SortedList<int, List<XmlNode>>();
        private static SortedList<int, XmlDocument> xmlDataCache = new SortedList<int, XmlDocument>();

        [MethodImpl(MethodImplOptions.Synchronized)]
        public static void load(string filePath, bool force)
        {
            if (force || dataDocument == null)
            {
                Stopwatch dataLoadTimer = Stopwatch.StartNew();

                dataTables.Clear();
                definitionFileNodeCache.Clear();
                xmlDataCache.Clear();

                XmlDocument localScopeDataDocument = new XmlDocument();
                SortedList<string, dataCache> localScopeNamedInstances = new SortedList<string, dataCache>();
                List<dataCache> localScopeOrderedInstances = new List<dataCache>();
                localScopeDataDocument.Load(filePath);

                XmlNodeList projectNodes = localScopeDataDocument.DocumentElement.SelectNodes("project");

                foreach (XmlNode projectNode in projectNodes)
                {
                    XmlNodeList dataNodes = projectNode.SelectNodes("data");
                    foreach (XmlNode dataNode in dataNodes)
                    {
                        string newDataName = projectNode.Attributes["name"].Value + "_" + dataNode.Attributes["name"].Value;
                        dataCache newDataCache = new dataCache();
                        newDataCache.reportCalculator = new reportCalculator(newDataCache);
                        newDataCache.dataNode = dataNode;

                        if (!localScopeNamedInstances.ContainsKey(newDataName))
                        {
                            localScopeNamedInstances.Add(newDataName, newDataCache);
                            localScopeOrderedInstances.Add(newDataCache);
                        }
                    }
                }
                dataCache.dataDocumentPath = filePath;
                dataDocument = localScopeDataDocument;
                namedInstances = localScopeNamedInstances;
                orderedInstances = localScopeOrderedInstances;
                dataTables.Clear();
                debug.write("data cache load completed in {0} ms", dataLoadTimer.Elapsed.TotalMilliseconds);
            }
        }

        public void createVirtualDataCache(string name, string projectName, string displayName, string dataTableName, string dataFetchFilter)
        {
            if (namedInstances.ContainsKey(name))
            {
                return;
            }
            dataCache newDataCache = new virtualDataCache(name, projectName, displayName, dataTableName, dataFetchFilter);
            newDataCache.reportCalculator = new reportCalculator(newDataCache);
            newDataCache.dataNode = dataNode;
            namedInstances.Add(name, newDataCache);
        }

        internal dataCache()
        {

        }

        public virtual void clear()
        {
            isReady = false;
            this.dealers.Clear();
            this.questionDefinitions.Clear();
            this.reportDefinitions.Clear();
            this.columnDefinitions.Clear();
            this.availableData.Clear();
            this.xmlData = null;
        }

        public static dataCache getDataCacheByProjectName(string projectName, string dataName)
        {
            return getNamedInstance(projectName + "_" + dataName);
        }

        public virtual dataColumn[] getDataColumnDefinitions()
        {
            return new List<dataColumn>(this.columnDefinitions.Values).ToArray();
        }

        private string GetDataFetchFilter()
        {
            if (string.IsNullOrEmpty(this.dataFetchFilter))
            {
                return "";
            }
            return this.dataFetchFilter;
        }

        public virtual dealerDefinition getDealer(string dealerCode)
        {
            return (this.dealers.ContainsKey(dealerCode) ? this.dealers[dealerCode] : null);
        }

        public static dataCache getNamedInstance(string name)
        {
            dataCache namedInstance = namedInstances[name];
            if (namedInstance == null)
            {
                return null;
            }
            namedInstance.load();
            return namedInstance;
        }

        public dealerDefinition getRootDealer()
        {
            return this.dealers["TR"];
        }

        public static DataTable getDataTable(string dataTableName)
        {
            if (!dataTables.ContainsKey(dataTableName))
            {
                DataTable table = db.query("SELECT * FROM " + dataTableName + " (NOLOCK)", new object[0]);
                dataTables.Add(dataTableName, table);
            }
            return dataTables[dataTableName];
        }

        private static List<reportMonth> getAvailableMonths(DataTable dataTable, string filter)
        {
            Stopwatch availableMonthsTimer = Stopwatch.StartNew();
            SortedList<int, reportMonth> availableMonths = new SortedList<int, reportMonth>();
            DataRow[] filteredRows = dataTable.Select(filter);
            foreach (DataRow row in filteredRows)
            {
                int monthAsInteger = Convert.ToInt32(row["YIL"]) * 12 + Convert.ToInt32(row["AY"]);
                if (!availableMonths.ContainsKey(monthAsInteger))
                {
                    availableMonths.Add(monthAsInteger, new reportMonth(Convert.ToInt32(row["YIL"]), Convert.ToInt32(row["AY"])));
                }
            }
            List<reportMonth> availableMonthsReversed = new List<reportMonth>(availableMonths.Values);
            availableMonthsReversed.Reverse();
            debug.write("available months calculated in {0} ms", availableMonthsTimer.Elapsed.TotalMilliseconds);
            return availableMonthsReversed;
        }

        private void load()
        {
            if (!isReady)
            {
                debug.write("Thread {0} Trying to acquire dataCache loader lock for data cache {1}", Thread.CurrentThread.ManagedThreadId, name);
                lock (this)
                {
                    debug.write("Thread {0} acquired dataCache loader lock for data cache {1}", Thread.CurrentThread.ManagedThreadId, name);
                    if (!isReady)
                    {
                        Stopwatch dataCacheLoadTimer = Stopwatch.StartNew();

                        this.clear();
                        this.reLoadDefinitionFile(organizationDefinitionFilePath);
                        DataTable table = getDataTable(this.dataTableName);

                        this.availableData = getAvailableMonths(table, this.GetDataFetchFilter());

                        lock (this.columnDefinitions)
                        {
                            this.columnDefinitions.Clear();
                            foreach (DataColumn column in table.Columns)
                            {
                                this.columnDefinitions.Add(column.ColumnName, new dataColumn(column.ColumnName, column.DataType));
                            }
                        }

                        Stopwatch selectTimer = Stopwatch.StartNew();
                        DataRow[] rows = table.Select(this.GetDataFetchFilter());
                        debug.write("Applied filter [{0}] to datatable with #{1} rows returned #{2} rows in {3} ms. ", this.GetDataFetchFilter(), table.Rows.Count, rows.Length, selectTimer.Elapsed.TotalMilliseconds);

                        foreach (DataRow row in rows)
                        {
                            dealerDefinition definition = this.getDealer(Convert.ToString(row["BAYII"]).Trim());
                            if ((definition != null))
                            {
                                cumulativeData data = definition.getCumulativeData(new reportMonth(Convert.ToInt32(row["YIL"]), Convert.ToInt32(row["AY"])));
                                data.baseCount++;
                                foreach (string str in this.questionDefinitions.Keys)
                                {
                                    if (table.Columns.Contains(str) && !string.IsNullOrEmpty(Convert.ToString(row[str])))
                                    {
                                        question local1 = data.questions[str];
                                        local1.baseCount++;
                                        if (!table.Columns[str].DataType.Equals(typeof(int)))
                                        {
                                            string[] strArray = Convert.ToString(row[str]).Split(new char[] { ',' });
                                            foreach (string str2 in strArray)
                                            {
                                                int optionValue;
                                                if (int.TryParse(str2, out optionValue))
                                                {
                                                    data.questions[str].appendOptionCount(optionValue);
                                                }
                                            }
                                        }
                                        else
                                        {
                                            data.questions[str].appendOptionCount(Convert.ToInt32(row[str]));
                                        }
                                    }
                                }
                            }
                        }
                        debug.write("dataCache [{0}] loaded in {1} ms", name, dataCacheLoadTimer.Elapsed.TotalMilliseconds);
                        isReady = true;
                    }
                }
            }
        }

        private static XmlDocument getDefinitionDocument(string definitionFilePath)
        {
            int hashCode = definitionFilePath.GetHashCode();

            if (!xmlDataCache.ContainsKey(hashCode))
            {
                XmlDocument definitionDocument = new XmlDocument();
                definitionDocument.Load(definitionFilePath);
                xmlDataCache.Add(hashCode, definitionDocument);
            }

            return xmlDataCache[hashCode];
        }

        private static List<XmlNode> applyDefinitionFileQuery(string definitionFilePath, string xPathQuery)
        {
            XmlDocument definitionDocument = getDefinitionDocument(definitionFilePath);

            int hashCode = (definitionFilePath + xPathQuery).GetHashCode();

            if (!definitionFileNodeCache.ContainsKey(hashCode))
            {
                XmlNodeList nodeList = definitionDocument.DocumentElement.SelectNodes(xPathQuery);
                List<XmlNode> matchingNodes = new List<XmlNode>();
                foreach (XmlNode matchingNode in nodeList)
                {
                    matchingNodes.Add(matchingNode);
                }
                definitionFileNodeCache.Add(hashCode, matchingNodes);
            }

            return definitionFileNodeCache[hashCode];
        }

        private void reLoadDefinitionFile(string definitionFilePath)
        {
            this.xmlData = getDefinitionDocument(definitionFilePath);

            List<XmlNode> list = applyDefinitionFileQuery(definitionFilePath, "organization/dealer");

            lock (this.dealers)
            {
                this.dealers.Clear();
                foreach (XmlNode node in list)
                {
                    dealerDefinition definition = new dealerDefinition(this, node);
                    if (this.dealers.ContainsKey(definition.getCode()))
                    {
                        throw new InvalidOperationException("The dealer with code " + definition.getCode() + " has already been defined");
                    }
                    this.dealers.Add(definition.getCode(), definition);
                }
                foreach (dealerDefinition definition2 in this.dealers.Values)
                {
                    List<XmlNode> list2 = applyDefinitionFileQuery(definitionFilePath, "organization/dealer" + ((definition2.sourceNode.Attributes["override_child_query"] != null) ? definition2.sourceNode.Attributes["override_child_query"].Value : ("[@parent='" + definition2.getCode() + "']")));
                    foreach (XmlNode node2 in list2)
                    {
                        definition2.children.Add(this.getDealer(node2.Attributes["code"].Value));
                    }
                }
            }
            lock (this.questionDefinitions)
            {
                this.questionDefinitions.Clear();
                List<XmlNode> list3 = applyDefinitionFileQuery(definitionFilePath, "questionnaire/question");
                foreach (XmlNode node3 in list3)
                {
                    questionDefinition definition3 = new questionDefinition(this, node3);
                    this.questionDefinitions.Add(definition3.getCode(), definition3);
                }
            }
            lock (this.reportDefinitions)
            {
                this.reportDefinitions.Clear();
                List<XmlNode> list4 = applyDefinitionFileQuery(definitionFilePath, "reports/report");
                foreach (XmlNode node4 in list4)
                {
                    reportDefinition definition4 = new reportDefinition(node4);
                    this.reportDefinitions.Add(definition4.getReportType(), definition4);
                }
            }
            this.reportCalculator.clearCache();
        }

        private string getOrganizationDefinitionFilePath(string filePath)
        {
            if (_organizationDefinitionFilePath == null)
            {
                string specificOrganizationFileName = dataNode.Attributes["organizationfile"] == null ? null : dataNode.Attributes["organizationfile"].Value;

                string dataFileName;

                if (specificOrganizationFileName != null)
                {
                    dataFileName = Path.Combine(Path.GetDirectoryName(filePath), specificOrganizationFileName);
                    if (!File.Exists(dataFileName))
                    {
                        throw new FileNotFoundException("The organization definition file for project " + projectName + " and data " + name + " could not be found (specific file search failed)");
                    }
                }
                else
                {
                    dataFileName = Path.Combine(Path.GetDirectoryName(filePath), projectName + name + ".xml");
                }

                if (!File.Exists(dataFileName))
                {
                    dataFileName = Path.Combine(Path.GetDirectoryName(filePath), projectName + ".xml");
                    if (!File.Exists(dataFileName))
                    {
                        throw new FileNotFoundException("The organization definition file for project " + projectName + " and data " + name + " could not be found");
                    }
                }
                _organizationDefinitionFilePath = dataFileName;
            }
            return _organizationDefinitionFilePath;
        }

        public virtual string organizationDefinitionFilePath
        {
            get
            {
                return getOrganizationDefinitionFilePath(dataDocumentPath);
            }
        }

        public virtual string dataFetchFilter
        {
            get
            {
                return dataNode.Attributes["filter"].Value;
            }
        }

        public virtual string dataTableName
        {
            get
            {
                return dataNode.ParentNode.Attributes["datatable"].Value;
            }
        }

        public virtual string projectName
        {
            get
            {
                return dataNode.ParentNode.Attributes["name"].Value;
            }
        }

        public virtual string name
        {
            get
            {
                return dataNode.Attributes["name"].Value;
            }
        }

        public virtual string displayName
        {
            get
            {
                return dataNode.Attributes["displayname"].Value;
            }
        }

        public virtual string hiddenState
        {
            get
            {
                return dataNode.Attributes["hiddenState"] != null ? dataNode.Attributes["hiddenState"].Value : null;
            }
        }

        public virtual bool isDefault
        {
            get
            {
                return dataNode.Attributes["default"] != null && dataNode.Attributes["default"].Value == "true";
            }
        }
    }
}

